// ==UserScript==
// @name         WzSLinker-网页股票联动伴侣
// @namespace    http://tampermonkey.net/
// @version      8.7
// @description  高效精准地高亮显示A股股票代码和名称 ，联动通达信、同花顺、东方财富、大智慧、指南针软件，它是一款能一键打通网页与本地股票软件的超级工具。
// @author       Bruce 微信: 370589873
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      localhost
// @connect      raw.gitcode.com
// ==/UserScript==

(function() {
    'use strict';

    // --- 1. 样式优化 - 区分股票代码和名称高亮 ---
    GM_addStyle(`
        /* 股票名称高亮样式 */
        .stock-name-highlight {
            color: #D32F2F !important; /* 深红色 */
            font-weight: bold !important;
            cursor: pointer !important;
            background-color: #FFF8E1 !important; /* 浅黄底色 */
            padding: 1px 3px !important;
            border-radius: 4px !important;
            display: inline !important;
            font-style: normal !important;
            box-shadow: 0 0 0 1px rgba(211, 47, 47, 0.2) !important;
            transition: all 0.2s ease !important;
        }
        .stock-name-highlight:hover {
            background-color: #FFECB3 !important;
            text-decoration: underline !important;
            box-shadow: 0 0 0 1px rgba(211, 47, 47, 0.4) !important;
        }

        /* 股票代码高亮样式 */
        .stock-code-highlight {
            color: #1976D2 !important; /* 深蓝色 */
            font-weight: bold !important;
            cursor: pointer !important;
            background-color: #E3F2FD !important; /* 浅蓝底色 */
            padding: 1px 3px !important;
            border-radius: 4px !important;
            display: inline !important;
            font-style: normal !important;
            box-shadow: 0 0 0 1px rgba(25, 118, 210, 0.2) !important;
            transition: all 0.2s ease !important;
        }
        .stock-code-highlight:hover {
            background-color: #BBDEFB !important;
            text-decoration: underline !important;
            box-shadow: 0 0 0 1px rgba(25, 118, 210, 0.4) !important;
        }
    `);

    // --- 2. 股票数据 ---
    let stockNameToCode = {};
    let stockCodeRegex;
    let stockNameRegex;
    let combinedRegex;

    // 从远程加载股票数据
    function loadStockData() {
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://raw.gitcode.com/AAheimao/1/raw/main/stock-data.json",
            onload: (response) => {
                try {
                    const stockData = JSON.parse(response.responseText);
                    stockNameToCode = stockData;
                    
                    // 重新初始化正则表达式
                    const stockNames = Object.keys(stockNameToCode);
                    // 按长度排序，优先匹配较长的名称
                    const sortedStockNames = stockNames.sort((a, b) => b.length - a.length);
                    const escapedStockNames = sortedStockNames.map(name => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
                    
                    // 调整正则表达式，放宽匹配条件但保持准确性
                    stockCodeRegex = /(00[0-9]{4}|30[0-9]{4}|60[0-9]{4}|68[0-9]{4})/g;
                    // 使用零宽断言替代单词边界，提高匹配能力
                    stockNameRegex = new RegExp(`(?<!\\w)(${escapedStockNames.join('|')})(?!\\w)`, 'g');
                    combinedRegex = new RegExp(`(${stockCodeRegex.source})|(${stockNameRegex.source})`, 'g');
                    
                    console.log("股票数据加载成功，共", stockNames.length, "条记录");
                    triggerHighlight();
                } catch (e) {
                    console.error("解析股票数据失败:", e);
                    useDefaultStockData();
                }
            },
            onerror: (error) => {
                console.error("加载股票数据失败:", error);
                useDefaultStockData();
            }
        });
    }

    // 使用默认股票数据
    function useDefaultStockData() {
        console.log("使用默认股票数据");
        stockNameToCode = {
            "上海建工": "600170",
            "中国电影": "600977",
            "园林股份": "605303",
            "方正科技": "600601",
            "大洋电机": "002249",
            "辰光医疗": "430300",
            "苏轴股份": "430418",
            "乐创技术": "430425",
            "海能技术": "430476",
            "峆一药业": "430478",
            "丰光精密": "430510",
            "星昊医药": "430017",
            "诺思兰德": "430047",
            "同辉信息": "430090",
            "华岭股份": "430139",
            "微创光电": "430198"
        };
        
        // 初始化正则表达式
        const stockNames = Object.keys(stockNameToCode);
        const sortedStockNames = stockNames.sort((a, b) => b.length - a.length);
        const escapedStockNames = sortedStockNames.map(name => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
        
        stockCodeRegex = /(00[0-9]{4}|30[0-9]{4}|60[0-9]{4}|68[0-9]{4})/g;
        stockNameRegex = new RegExp(`(?<!\\w)(${escapedStockNames.join('|')})(?!\\w)`, 'g');
        combinedRegex = new RegExp(`(${stockCodeRegex.source})|(${stockNameRegex.source})`, 'g');
    }

    // --- 3. 核心高亮逻辑 ---

    // 性能优化：存储已处理的父节点，而非文本节点
    const processedParentNodes = new WeakSet();
    // 允许扫描的特殊标签列表，解决部分模块无法读取问题
    const ALLOWED_TAGS = ['DIV', 'SPAN', 'P', 'A', 'TD', 'TH', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'B', 'I', 'STRONG', 'EM', 'FONT', 'ARTICLE', 'SECTION', 'MAIN'];

    // ContextualWalker 类的定义
    class ContextualWalker {
        constructor(rootNode, regex, highlightFn) {
            this.root = rootNode;
            this.regex = regex;
            this.highlightFn = highlightFn;
            this.textNodes = [];
            this.scan();
            this.process();
        }
        scan() {
            const walker = document.createTreeWalker(this.root, NodeFilter.SHOW_TEXT, {
                acceptNode: (node) => {
                    const parent = node.parentElement;
                    if (!parent) return NodeFilter.FILTER_REJECT;
                    
                    // 检查是否已处理过该父节点
                    if (processedParentNodes.has(parent)) return NodeFilter.FILTER_REJECT;
                    
                    // 排除特定功能性标签
                    const excludeTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'];
                    if (excludeTags.includes(parent.tagName)) return NodeFilter.FILTER_REJECT;
                    
                    // 排除已高亮的元素内部
                    if (parent.closest('.stock-name-highlight, .stock-code-highlight')) return NodeFilter.FILTER_REJECT;
                    
                    // 允许特定标签的内容被扫描（放宽限制）
                    if (!ALLOWED_TAGS.includes(parent.tagName) && !parent.closest(ALLOWED_TAGS.join(','))) {
                        return NodeFilter.FILTER_REJECT;
                    }
                    
                    // 排除空文本节点
                    if (node.nodeValue.trim() === '') return NodeFilter.FILTER_REJECT;
                    
                    return NodeFilter.FILTER_ACCEPT;
                }
            });
            while (walker.nextNode()) {
                this.textNodes.push(walker.currentNode);
            }
        }
        process() {
            // 按父节点分组处理，确保同一标签内的所有内容都能被完整处理
            const parentGroups = new Map();
            
            for (const node of this.textNodes) {
                const parent = node.parentElement;
                if (!parentGroups.has(parent)) {
                    parentGroups.set(parent, []);
                }
                parentGroups.get(parent).push(node);
            }
            
            // 处理每个父节点下的所有文本节点
            for (const [parent, nodes] of parentGroups) {
                if (processedParentNodes.has(parent)) continue;
                
                let context = { 
                    text: nodes.map(n => n.nodeValue).join(''), 
                    nodes: nodes, 
                    maxLookAhead: 20 
                };
                
                if (context.text) {
                    this.highlightFn(context, this.regex);
                    processedParentNodes.add(parent);
                }
            }
        }
        areNodesAdjacent(node1, node2) {
            let sibling = node1.nextSibling;
            while (sibling) {
                if (sibling === node2) return true;
                if (sibling.nodeType === Node.ELEMENT_NODE && sibling.textContent.trim() === '' && 
                    getComputedStyle(sibling).display.includes('inline')) {
                    sibling = sibling.nextSibling;
                    continue;
                }
                return false;
            }
            let parent = node1.parentNode;
            while (parent && parent !== this.root) {
                sibling = parent.nextSibling;
                while (sibling) {
                    if (sibling.contains(node2)) return true;
                    if (sibling.nodeType === Node.ELEMENT_NODE && sibling.textContent.trim() === '' && 
                        getComputedStyle(sibling).display.includes('inline')) {
                        sibling = sibling.nextSibling;
                        continue;
                    }
                    return false;
                }
                parent = parent.parentNode;
            }
            return false;
        }
    }

    // applyHighlight 函数的定义
    function applyHighlight(context, regex) {
        let originalText = context.text;
        let modifiedText = originalText;
        const matches = [];
        let match;
        
        // 首先收集所有匹配项
        regex.lastIndex = 0;
        while ((match = regex.exec(originalText)) !== null) {
            const matchText = match[0];
            const isCode = !!match[1];
            const startIndex = match.index;
            const endIndex = startIndex + matchText.length;
            
            // 检查这个匹配是否已经被之前的匹配覆盖
            const isOverlapping = matches.some(m => 
                !(endIndex <= m.startIndex || startIndex >= m.endIndex)
            );
            
            // 只添加不重叠的匹配，优先保留较长的匹配
            if (!isOverlapping) {
                matches.push({
                    text: matchText,
                    isCode: isCode,
                    startIndex: startIndex,
                    endIndex: endIndex,
                    length: matchText.length
                });
            }
        }
        
        // 按起始位置排序，确保从左到右处理
        matches.sort((a, b) => a.startIndex - b.startIndex);
        
        // 应用所有匹配
        let offset = 0;
        const fragment = document.createDocumentFragment();
        let lastIndex = 0;
        
        for (const match of matches) {
            const stockCode = match.isCode ? match.text : (stockNameToCode[match.text] || null);
            if (!stockCode) continue;
            
            // 添加非匹配部分
            if (match.startIndex > lastIndex) {
                const textNode = document.createTextNode(
                    originalText.substring(lastIndex, match.startIndex)
                );
                fragment.appendChild(textNode);
            }
            
            // 添加高亮部分
            const highlightSpan = document.createElement('span');
            highlightSpan.className = match.isCode ? 'stock-code-highlight' : 'stock-name-highlight';
            highlightSpan.textContent = match.text;
            highlightSpan.dataset.stockCode = stockCode;
            fragment.appendChild(highlightSpan);
            
            lastIndex = match.endIndex;
        }
        
        // 添加剩余部分
        if (lastIndex < originalText.length) {
            const textNode = document.createTextNode(originalText.substring(lastIndex));
            fragment.appendChild(textNode);
        }
        
        // 清除原有内容并插入新内容
        if (context.nodes.length > 0) {
            const firstNode = context.nodes[0];
            const parent = firstNode.parentNode;
            
            // 清除所有相关节点
            context.nodes.forEach(node => node.remove());
            
            // 插入新内容
            parent.appendChild(fragment);
        }
    }

    // --- 4. 联动与事件处理 ---

    function sendToLocalBridge(stockCode) {
        console.log(`发送代码 ${stockCode} 到本地服务进行广播...`);
        GM_xmlhttpRequest({
            method: "POST",
            url: "http://localhost:3000/tdx",
            data: JSON.stringify({ code: stockCode } ),
            headers: { "Content-Type": "application/json" },
            onload: (response) => console.log("联动广播请求成功发送。", JSON.parse(response.responseText)),
            onerror: (error) => {
                console.error("无法连接到本地服务。", error);
                alert("联动失败：无法连接到本地服务。");
            }
        });
    }

    document.body.addEventListener('click', (event) => {
        const target = event.target.closest('.stock-name-highlight, .stock-code-highlight');
        if (target && target.dataset.stockCode) {
            event.preventDefault();
            event.stopPropagation();
            sendToLocalBridge(target.dataset.stockCode);
        }
    }, true);

    // --- 5. 动态内容处理与周期性扫描 ---

    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }

    const triggerHighlight = debounce(() => {
        if (combinedRegex) {
            new ContextualWalker(document.body, combinedRegex, applyHighlight);
        }
    }, 300); // 缩短防抖时间，提高响应速度

    // 响应式高亮 - 扩大观察范围
    const observer = new MutationObserver(() => {
        triggerHighlight();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
        characterData: true,
        attributes: false // 关闭属性观察，减少性能消耗
    });

    // 周期性高亮 - 增加扫描频率
    setInterval(() => {
        triggerHighlight();
    }, 1000); // 缩短扫描间隔

    // --- 6. 初始执行 ---
    window.addEventListener('load', () => {
        loadStockData();
        // 分阶段执行，确保不同加载阶段的内容都能被处理
        setTimeout(triggerHighlight, 500);
        setTimeout(triggerHighlight, 2000);
        setTimeout(triggerHighlight, 5000);
    });

    // 额外监听DOMContentLoaded事件，确保初始内容被处理
    document.addEventListener('DOMContentLoaded', () => {
        setTimeout(triggerHighlight, 300);
    });

})();
